package org.budo.netty.servlet.server.handler.servlet;

import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.EventListener;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.Filter;
import javax.servlet.FilterRegistration;
import javax.servlet.RequestDispatcher;
import javax.servlet.Servlet;
import javax.servlet.ServletContextAttributeListener;
import javax.servlet.ServletContextListener;
import javax.servlet.ServletRegistration;
import javax.servlet.ServletRequestAttributeListener;
import javax.servlet.ServletRequestListener;
import javax.servlet.ServletRegistration.Dynamic;
import javax.servlet.descriptor.JspConfigDescriptor;
import javax.servlet.http.HttpSessionAttributeListener;

import org.budo.netty.servlet.server.BudoServletServer;
import org.budo.netty.servlet.server.handler.servlet.dispatcher.BudoNameRequestDispatcher;
import org.budo.netty.servlet.server.handler.servlet.dispatcher.BudoPathRequestDispatcher;
import org.budo.netty.servlet.server.handler.servlet.registration.BudoFilterRegistration;
import org.budo.netty.servlet.server.handler.servlet.registration.BudoServletRegistration;
import org.budo.netty.servlet.server.resource.Resource;
import org.budo.netty.servlet.server.resource.loader.FileResource;
import org.budo.netty.servlet.server.resource.loader.ResourceLoader;
import org.budo.netty.servlet.util.InstanceFactory;

import lombok.extern.slf4j.Slf4j;

/**
 * @author lmw
 */
@Slf4j
public class DefaultBudoServletContext extends AbstractBudoServletContext {
    private Map<String, String> initParameterMap = new HashMap<String, String>();

    private Map<String, FilterRegistration> filterRegistrationMap = new HashMap<String, FilterRegistration>();

    private Map<String, ServletRegistration> servletRegistrationMap = new HashMap<String, ServletRegistration>();

    private Map<String, Object> attributeMap = new HashMap<String, Object>();

    private List<EventListener> listeners = new ArrayList<EventListener>();

    private BudoServletServer budoServletServer;

    private String contextPath = "";

    public DefaultBudoServletContext(BudoServletServer budoServletServer) {
        this.budoServletServer = budoServletServer;
    }

    /**
     * 生命周期
     */
    public void init() {
        log.info("#62 DefaultBudoServletContext init, this=" + this);

        this.budoServletServer.getEventBus().servletContextInitialized(this); // 先 Listener

        InstanceFactory.initInstance(this); // 再 Filter Servlet
    }

    /**
     * 生命周期
     */
    public void destroy() {
        log.info("#62 DefaultBudoServletContext destroy, this=" + this);

        InstanceFactory.destroyInstance();
        this.budoServletServer.getEventBus().servletContextDestroyed(this);
    }

    @Override
    public String getMimeType(String file) {
        log.info("#87 getMimeType, file=" + file);
        return "text/html";
    }

    @Override
    public <T extends EventListener> void addListener(T listener) {
        if (listener instanceof ServletContextListener //
                || listener instanceof ServletRequestListener //
                || listener instanceof ServletContextAttributeListener //
                || listener instanceof HttpSessionAttributeListener//
                || listener instanceof ServletRequestAttributeListener) {
            this.listeners.add(listener);
            return;
        }

        throw new UnsupportedOperationException("#95 未支持 " + listener.getClass());
    }

    @Override
    public void addListener(Class<? extends EventListener> listenerClass) {
        EventListener listenerInstance = InstanceFactory.listenerInstance(listenerClass);
        this.addListener(listenerInstance);
    }

    @Override
    public void addListener(String className) {
        Class<? extends EventListener> listenerType = InstanceFactory.listenerType(className);
        this.addListener(listenerType);
    }

    @Override
    public void setAttribute(String name, Object value) {
        this.attributeMap.put(name, value);

        this.budoServletServer.getEventBus().servletContextAttributeSetted(this, name, value);
    }

    @Override
    public String getContextPath() {
        return this.contextPath;
    }

    @Override
    public Object getAttribute(String name) {
        return this.attributeMap.get(name);
    }

    @Override
    public Enumeration<String> getAttributeNames() {
        return Collections.enumeration(this.attributeMap.keySet());
    }

    @Override
    public RequestDispatcher getRequestDispatcher(String path) {
        return new BudoPathRequestDispatcher(path);
    }

    @Override
    public RequestDispatcher getNamedDispatcher(String name) {
        return new BudoNameRequestDispatcher(name);
    }

    @Override
    public FilterRegistration.Dynamic addFilter(String filterName, Class<? extends Filter> filterClass) {
        BudoFilterRegistration registration = new BudoFilterRegistration(filterName, filterClass.getName(), this);
        this.filterRegistrationMap.put(filterName, registration);
        return registration;
    }

    @Override
    public FilterRegistration getFilterRegistration(String filterName) {
        return this.filterRegistrationMap.get(filterName);
    }

    @Override
    public Dynamic addServlet(String servletName, Class<? extends Servlet> servletClass) {
        return this.addServlet(servletName, servletClass.getName());
    }

    @Override
    public javax.servlet.ServletRegistration.Dynamic addServlet(String servletName, String servletClass) {
        BudoServletRegistration registration = new BudoServletRegistration(servletName, servletClass, this);
        this.servletRegistrationMap.put(servletName, registration);
        return registration;
    }

    @Override
    public Map<String, ? extends ServletRegistration> getServletRegistrations() {
        return this.servletRegistrationMap;
    }

    @Override
    public Map<String, ? extends FilterRegistration> getFilterRegistrations() {
        return this.filterRegistrationMap;
    }

    @Override
    public Enumeration<Servlet> getServlets() {
        List<Servlet> servlets = new ArrayList<Servlet>();
        Collection<ServletRegistration> servletRegistrations = this.servletRegistrationMap.values();

        for (ServletRegistration servletRegistration : servletRegistrations) {
            Servlet servlet = InstanceFactory.servletInstance(servletRegistration.getClassName(), this);
            servlets.add(servlet);
        }

        return Collections.enumeration(servlets);
    }

    @Override
    public Enumeration<String> getInitParameterNames() {
        return Collections.enumeration(this.initParameterMap.keySet());
    }

    @Override
    public String getInitParameter(String name) {
        return this.initParameterMap.get(name);
    }

    /**
     * @return 如果已有值，则返回false，不设值
     */
    @Override
    public boolean setInitParameter(String name, String value) {
        if (this.initParameterMap.containsKey(name)) {
            log.warn("#212 setInitParameter, name=" + name + ", value=" + value + ", exists " + this.initParameterMap.get(name));
            return false;
        }

        this.initParameterMap.put(name, value);
        return true;
    }

    @SuppressWarnings("unchecked")
    @Override
    public <T> List<T> listListener(Class<T> type) {
        List<T> list = new ArrayList<T>();

        for (EventListener listener : this.listeners) {
            if (type.isAssignableFrom(listener.getClass())) { // 根据类型查询
                list.add((T) listener);
            }
        }

        return list;
    }

    @Override
    public InputStream getResourceAsStream(String path) {
        ResourceLoader resourceLoader = this.budoServletServer.getResourceLoader();
        Resource resource = resourceLoader.getResource(path);
        if (null != resource) {
            return resource.getInputStream();
        }

        log.debug("#227 getResourceAsStream return null, path={}", path);
        return null;
    }

    @Override
    public URL getResource(String path) throws MalformedURLException {
        ResourceLoader resourceLoader = this.budoServletServer.getResourceLoader();
        Resource resource = resourceLoader.getResource(path);
        if (null != resource) {
            return resource.getURL();
        }

        log.debug("#219 getResource return null, path={}", path);
        return null;
    }

    @Override
    public String getRealPath(String path) {
        ResourceLoader resourceLoader = this.budoServletServer.getResourceLoader();
        Resource resource = resourceLoader.getResource(path);
        if (null != resource && resource instanceof FileResource) {
            return ((FileResource) resource).getRealPath();
        }

        log.debug("#249 getRealPath return null, path={}", path);
        return null;
    }

    @Override
    public void log(String message) {
        log.warn("#226 log, message=" + message + ", this=" + this);
    }

    @Override
    public void log(String message, Throwable throwable) {
        log.error("#211 log, message=" + message + ", throwable=" + throwable + ", this=" + this, throwable);
    }

    @Override
    public int getMajorVersion() {
        return 3;
    }

    @Override
    public int getMinorVersion() {
        return 0;
    }

    @Override
    public int getEffectiveMajorVersion() {
        return 3;
    }

    @Override
    public int getEffectiveMinorVersion() {
        return 0;
    }

    @Override
    public JspConfigDescriptor getJspConfigDescriptor() {
        return null;
    }

    @Override
    public ClassLoader getClassLoader() {
        return this.getClass().getClassLoader();
    }

    @Override
    public String getServerInfo() {
        return "-budo-server-";
    }

    @Override
    public BudoServletServer getBudoServletServer() {
        return this.budoServletServer;
    }

    @Override
    public String toString() {
        return super.toString() + ", initParameterMap=" + this.initParameterMap //
                + ", servletRegistrations=" + this.getServletRegistrations() //
                + ", filterRegistrations=" + this.getFilterRegistrations();
    }
}